﻿//------------------------------------------------------------------------------------------------- 
// <copyright file="ContextFilterAttribute.cs" company="Christopher Hines">
// Copyright (c) Christopher Hines. All rights reserved.
// </copyright>
// <summary>
//    Applies a type-specific filter to a decorated class
// </summary>
//-------------------------------------------------------------------------------------------------

namespace Copperfield
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>
    /// The base class for an attribute used to shape the use of specific object type
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    public abstract class ContextFilterAttribute : Attribute
    {
        /// <summary>
        /// Gets the context values
        /// </summary>
        private readonly IEnumerable<object> contextIds;

        /// <summary>
        /// Gets context type
        /// </summary>
        private readonly Type contextType;

        /// <summary>
        /// Initializes a new instance of the ContextFilterAttribute class
        /// </summary>
        /// <param name="filterType">The type of filter to be applied</param>
        protected internal ContextFilterAttribute(Type filterType)
        {
            if (!filterType.IsEnum)
            {
                throw new ArgumentException("The filter type must be a type of Enum", "filterType");
            }

            this.contextIds = TypeUtility.GetCachedValues(filterType);

            this.contextType = filterType;
        }

        /// <summary>
        /// Initializes a new instance of the ContextFilterAttribute class
        /// </summary>
        /// <param name="filterType">The type of filter to be applied</param>
        /// <param name="value">The value of the filter</param>
        /// <param name="excluded">A value indicating whether or not to excluded the provided value</param>
        protected internal ContextFilterAttribute(Type filterType, object value, bool excluded = false)
        {
            if (!filterType.IsInstanceOfType(value))
            {
                throw new ArgumentException("The provided value must match the context type", "value");
            }

            var values = new[] { value };

            this.contextIds = filterType.IsEnum && excluded
                              ? TypeUtility.GetCachedValues(filterType).Except(values)
                              : values;

            this.contextType = filterType;
        }

        /// <summary>
        /// Initializes a new instance of the ContextFilterAttribute class
        /// </summary>
        /// <param name="filterType">The type of filter</param>
        /// <param name="values">The filter values</param>
        /// <param name="excluded">A value indicating whether or not the specified values should be excluded</param>
        protected internal ContextFilterAttribute(Type filterType, object[] values, bool excluded = false)
        {
            if (values.Any(x => !filterType.IsInstanceOfType(x)))
            {
                throw new ArgumentException("All provided values must match the context type", "values");
            }

            this.contextIds = filterType.IsEnum && excluded
                              ? TypeUtility.GetCachedValues(filterType).Except(values)
                              : values;

            this.contextType = filterType;
        }

        /// <summary>
        /// Gets the filter values
        /// </summary>
        public List<object> Values
        {
            get { return this.contextIds.ToList(); }
        }

        /// <summary>
        /// Gets the context type
        /// </summary>
        public Type ContextType
        {
            get { return this.contextType; }
        }

        /// <summary>
        /// Determines whether a filter applies to a specific type
        /// </summary>
        /// <param name="type">The type to compare the filter against</param>
        /// <returns>A value indicating whether the filter applies to the specified type</returns>
        public bool AppliesTo(Type type)
        {
            return this.contextType.IsAssignableFrom(type);
        }
    }
}
